import EventEmitter from "../eventemitter/EventEmitter";

export enum WatcherEmit {
    Update = 'Update',

    Add = 'Add',

    Remove = 'Remove',

    Replace = 'Replace',
}

export default class ArrayWatcher<T> extends EventEmitter {


    private _value: T[];

    constructor(value: T[] = []) {
        super();
        this._value = [...value];
    }

    getArray(index: number, count: number) {
        return this._value.slice(index, count);
    }

    getAll() {
        return [...this._value];
    }

    get(index: number) {
        if (index >= 0 && index < this._value.length) {
            return this._value[index];
        }
    }

    length() {
        return this._value.length;
    }

    indexOf(v: T) {
        return this._value.indexOf(v);
    }

    include(v: T) {
        return this.indexOf(v) >= 0;
    }

    push(...items: T[]) {
        const length = this.length()
        this._value.push(...items);
        this.emit(WatcherEmit.Add, this, items, length);
        this.emit(WatcherEmit.Update, this, WatcherEmit.Add);
    }

    unshift(...items: T[]) {
        this._value.unshift(...items);
        this.emit(WatcherEmit.Add, this, items, 0);
        this.emit(WatcherEmit.Update, this, WatcherEmit.Add);
    }

    insert(index: number, ...items: T[]) {
        if (index < 0) {
            this.unshift(...items);
            return
        }
        if (index >= this._value.length) {
            this.push(...items);
            return;
        }
        this._value.splice(index, 0, ...items);
        this.emit(WatcherEmit.Add, this, items, index);
        this.emit(WatcherEmit.Update, this, WatcherEmit.Add);
    }


    remove(value: T, emit: boolean = true) {
        const index = this._value.indexOf(value)
        if (index < 0) {
            return false;
        }
        return this.removeIndex(index, 1, emit).length > 0
    }

    removeFilter(filter: (value: T) => boolean, emit: boolean = true) {
        this.removeArray(this._value.filter(filter), emit);
    }

    removeIndex(index: number, deleteCount: number = 1, emit: boolean = true) {
        const remove = this._value.splice(index, deleteCount);
        if (emit) {
            this.emit(WatcherEmit.Remove, this, remove);
            this.emit(WatcherEmit.Update, this, WatcherEmit.Remove);
        }
        return remove
    }

    removeArray(array: T[], emit: boolean = true) {
        if (array.length <= 0) return false;
        const filter = this._value.filter((v) => array.indexOf(v) < 0);
        this._value.splice(0, this.length(), ...filter);
        if (emit) {
            this.emit(WatcherEmit.Remove, this, array);
            this.emit(WatcherEmit.Update, this, WatcherEmit.Remove);
        }
        return true;
    }

    removeAll(emit: boolean = true): T[] {
        const removeAll = [...this._value]
        this._value.length = 0;
        if (emit) {
            this.emit(WatcherEmit.Remove, this, removeAll);
            this.emit(WatcherEmit.Update, this, WatcherEmit.Remove);
        }
        return removeAll
    }

    sort(compareFn: (a: T, b: T) => number, emit: boolean = true) {
        this._value.sort(compareFn);
        if (emit) {
            this.emit(WatcherEmit.Update, this, WatcherEmit.Update);
        }
        return this
    }

    replace(index: number, items: T, emit: boolean = true) {
        if (index < 0 || index >= this.length()) {
            return;
        }
        const old = this._value.splice(index, 1, items);
        if (emit) {
            this.emit(WatcherEmit.Replace, this, [items], [old]);
            this.emit(WatcherEmit.Update, this, WatcherEmit.Replace);
        }
        return old;
    }

    replaceAll(items: T[], emit: boolean = true) {
        const old = this._value.splice(0, this.length(), ...items);
        if (emit) {
            this.emit(WatcherEmit.Replace, this, items, old);
            this.emit(WatcherEmit.Update, this, WatcherEmit.Replace);
        }
        return old;
    }

    map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[] {
        return this._value.map(callbackfn)
    }

    filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[] {
        return this._value.filter(predicate, thisArg)
    }

    foreach(call: (value: T, index: number, array: T[]) => boolean | void) {
        for (let index = 0; index < this._value.length; index++) {
            const element = this._value[index];
            if (call(element, index, this._value)) {
                break;
            }
        }
    }

    listenerUpdate(call: (t: ArrayWatcher<T>, emit: WatcherEmit) => void) {
        this.on(WatcherEmit.Update, call, this)
        return () => {
            this.off(WatcherEmit.Update, call, this);
        };
    }

    listenerAdd(call: (t: ArrayWatcher<T>, add: T[], insert: number) => void) {
        this.on(WatcherEmit.Add, call, this)
        return () => {
            this.off(WatcherEmit.Add, call, this);
        };
    }

    listenerRemove(call: (t: ArrayWatcher<T>, remove: T[]) => void) {
        this.on(WatcherEmit.Remove, call, this)
        return () => {
            this.off(WatcherEmit.Remove, call, this);
        };
    }

    listenerReplace(call: (t: ArrayWatcher<T>, replace: T[], old: T[]) => void) {
        this.on(WatcherEmit.Replace, call, this)
        return () => {
            this.off(WatcherEmit.Replace, call, this);
        };
    }
}